<?php

namespace pay;

use app\scan_bag\service\ScanBagV2Service;
use Psr\Http\Message\StreamInterface;
use think\response\Json;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;

/**
 * 微信支付
 */
class WechatPay
{
    //构造一个APIv3客户端实例
    private mixed $instance;
    //商户号
    public string $merchantId = '1617999716';
    //商户API证书的证书序列号
    public string $merchantCertificateSerial = '7F060470DE5BA89B5F270E0DA5B3E06EB37EA787';
    //商户API证书APIv3密钥
    public string $merchantAPIv3 = 'cdShu5UbwdCjpeLnYYcz3oChAWQTOGFN';

    /**
     * 构造方法
     */
    public function __construct()
    {
        $cert = app()->getRootPath() . 'extend/pay/wechatPayCert/';
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = file_get_contents($cert . 'apiclient_key.pem');
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        //从本地文件中加载微信支付平台证书，用来验证微信支付应答的签名
        $platformCertificateFilePath = file_get_contents($cert . 'wechatpay.pem');
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        //从微信支付平台证书中获取证书序列号
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        //构造一个 APIv3 客户端实例
        $this->instance = Builder::factory([
            'mchid' => $this->merchantId,
            'serial' => $this->merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);
    }

    /**
     * 获取微信支付平台证书
     * @return StreamInterface
     */
    public function getCertificates(): StreamInterface
    {
        //发送请求
        $resp = $this->instance->chain('v3/certificates')->get();
        return $resp->getBody();
    }

    /**
     * 生成预支付交易单
     * @param string $openid
     * @param string $out_trade_no
     * @param float $money
     * @param string $appid
     * @param string $description
     * @param string $notify_url
     * @return array
     */
    public function payTransactionsJsapi(string $openid, string $out_trade_no, float $money, string $appid, string $description, string $notify_url): array
    {
        $time_expire = date('Y-m-d') . 'T' . date('H:i:s', time() + 3600);
        try {
            $resp = $this->instance
                ->chain('v3/pay/transactions/jsapi')
                ->post([
                    // JSON请求体
                    'json' => [
                        "appid" => $appid,
                        "mchid" => $this->merchantId,
                        "description" => $description,
                        "out_trade_no" => $out_trade_no,
                        "time_expire" => $time_expire . "+08:00",
                        "attach" => $out_trade_no,
                        "notify_url" => $notify_url,
                        "amount" => [
                            "total" => intval($money * 100),
                            "currency" => "CNY",
                        ],
                        "payer" => [
                            "openid" => $openid,
                        ],
                    ],
                    'headers' => ['Accept' => 'application/json']
                ]);
            //返回状态码
            $code = $resp->getStatusCode();
            if (in_array($code, [200, 204])) {
                return ['bool' => true, 'msg' => json_decode($resp->getBody()->getContents(), true)];
            }
            return ['bool' => false, 'msg' => $resp->getBody()];
        } catch (\Exception $e) {
            return ['bool' => false, 'msg' => $e->getMessage()];
        }
    }

    /**
     * 生成前端调起支付需要的参数
     * @param string $appid
     * @param string $prepay_id
     * @return array
     */
    public function jsPaySignMd5(string $appid, string $prepay_id): array
    {
        $cert = app()->getRootPath() . 'extend/pay/wechatPayCert/';
        $merchantPrivateKeyFilePath = file_get_contents($cert . 'apiclient_key.pem');
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
        $params = [
            'appId' => $appid,
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr' => Formatter::nonce(),
            'package' => 'prepay_id=' . $prepay_id,
        ];
        $params += ['paySign' => Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($params)),
            $merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];
        return $params;
    }

    /**
     * 支付回调通知
     * @param string $type
     * @return Json
     */
    public function wechatPayNotice(string $type): Json
    {
        $inWechatpaySignature = request()->header('Wechatpay-Signature');
        $inWechatpayTimestamp = request()->header('Wechatpay-Timestamp');
        $inWechatpayNonce = request()->header('Wechatpay-Nonce');
        $inBody = file_get_contents('php://input');
        //根据通知的平台证书序列号，查询本地平台证书文件，
        $cert = app()->getRootPath() . 'extend/pay/wechatPayCert/';
        $platformPublicKeyInstance = Rsa::from(file_get_contents($cert . 'wechatpay.pem'), Rsa::KEY_TYPE_PUBLIC);
        //检查通知时间偏移量，允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );
        $inBodyResourceArray = [];
        if ($timeOffsetStatus && $verifiedStatus) {
            //转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            //使用PHP7的数据解构语法，从Array中解构并赋值变量
            ['resource' => [
                'ciphertext' => $ciphertext,
                'nonce' => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $this->merchantAPIv3, $nonce, $aad);
            // 把解密后的文本转换为PHP Array数组
            $inBodyResourceArray = (array)json_decode($inBodyResource, true);
        }
        //根据类型处理结果
        if ($inBodyResourceArray) {
            switch ($type) {
                case 'ScanBag':
                    $ScanBagV2Service = new ScanBagV2Service();
                    $ScanBagV2Service->scanBagOrder($inBodyResourceArray);
                    break;
                default:
                    break;
            }
        }
        return jsonReturn(1, []);
    }

}